﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WOS.Common
{
    public enum DataSizeFormat
    {
        Megabytes = 0,
        Default = Megabytes,
    }
    public enum DataSizeMode
    {
        Exact,
        Approximate
    }
    public sealed class DataSize
    {
        #region Static Members
        public static readonly DataSize Unknown = new DataSize();
        public static readonly DataSize Zero = new DataSize(DataSizeMode.Exact, 0);
        #endregion
        #region Private Fields
        private readonly DataSizeMode mode;
        private readonly long? size;
        #endregion
        #region Constructors
        private DataSize()
        {
            this.mode = DataSizeMode.Approximate;
            this.size = null;
        }

        public DataSize(DataSizeMode mode, long size)
        {
            this.mode = mode;
            this.size = size;
        }
        #endregion

        #region Public Members
        public long? GetBytes()
        {
            return this.size;
        }

        public DataSizeMode Mode
        {
            get { return this.mode; }
        }

        public string ToString(DataSizeFormat format)
        {
            var sb = new StringBuilder();

            if (this.mode == DataSizeMode.Approximate)
                sb.Append("~");

            if (!size.HasValue)
            {
                sb.Append("? ");

                switch (format)
                {
                    case DataSizeFormat.Megabytes: sb.AppendFormat("MB"); break;
                    default: throw new NotSupportedException();
                }
            }

            if (size.HasValue)
            {
                switch (format)
                {
                    case DataSizeFormat.Megabytes: sb.AppendFormat("{0:0.00} MB", 1.0M * size.Value / (1024 * 1024)); break;
                    default: throw new NotSupportedException();
                }
            }

            return sb.ToString().TrimEnd();
        }

        public static DataSize operator +(DataSize current, long increase)
        {
            return new DataSize(current.mode, (current.size ?? 0) + increase);
        }

        public static DataSize operator +(DataSize current, DataSize increase)
        {
            var sum = current.size + increase.size;

            if (current.mode == increase.mode)
                return new DataSize(current.mode, sum.HasValue ? sum.Value : 0);

            return new DataSize(DataSizeMode.Approximate, sum.HasValue ? sum.Value : 0);
        }

        public static bool operator >=(DataSize left, DataSize right)
        {
            if (Object.ReferenceEquals(left, right))
                return true;

            if (Object.ReferenceEquals(left, null) || Object.ReferenceEquals(right, null))
                return false;

            return left.size >= right.size;
        }

        public static bool operator <=(DataSize left, DataSize right)
        {
            if (Object.ReferenceEquals(left, right))
                return true;

            if (Object.ReferenceEquals(left, null) || Object.ReferenceEquals(right, null))
                return false;

            return left.size <= right.size;
        }

        public static bool operator ==(DataSize left, DataSize right)
        {
            if (Object.ReferenceEquals(left, right))
                return true;

            if (Object.ReferenceEquals(left, null) || Object.ReferenceEquals(right, null))
                return false;

            return left.Equals(right);
        }

        public static bool operator !=(DataSize left, DataSize right)
        {
            return !(left == right);
        }

        public static bool AreSimilar(DataSize left, DataSize right)
        {
            if (left == right)
                return true;

            if (left.Mode == DataSizeMode.Exact && right.Mode == DataSizeMode.Exact)
                return false;

            if (!left.GetBytes().HasValue || !right.GetBytes().HasValue)
                return false;

            var lRanges = new[] { left.GetBytes().Value, left.GetBytes().Value };
            var rRanges = new[] { right.GetBytes().Value, right.GetBytes().Value };

            if (left.Mode == DataSizeMode.Approximate)
                lRanges = new[] { lRanges[0] / 100 * 95, lRanges[1] / 100 * 105 };

            if (right.Mode == DataSizeMode.Approximate)
                rRanges = new[] { rRanges[0] / 100 * 95, rRanges[1] / 100 * 105 };

            return lRanges[1] >= rRanges[0] && lRanges[0] <= rRanges[1];
        }

        public static DataSize Average(DataSize[] sizes)
        {
            if (sizes.Length == 0)
                return DataSize.Zero;

            foreach (var size in sizes)
                if (!size.size.HasValue)
                    throw new NotSupportedException();

            var mode = DataSizeMode.Approximate;
            var value = Convert.ToInt64(Math.Round(sizes.Select(x => x.size.Value).Average()));

            if (sizes.All(x => x.Mode == DataSizeMode.Exact))
                mode = DataSizeMode.Exact;

            return new DataSize(mode, value);
        }
        #endregion
        #region Overridden Members
        public override string ToString()
        {
            return this.ToString(DataSizeFormat.Default);
        }

        public override int GetHashCode()
        {
            return this.size.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            var second = obj as DataSize;

            return second != null && second.mode == this.mode && second.size == this.size;
        }
        #endregion
    }
    
}
